import std.stdio;
import std.array;
import std.process;
import std.string;
import std.c.linux.linux;
import std.conv;
import std.math;
import std.regexp;
import std.algorithm;

const STDIN_FILENO=0;

int toInt(string s){
	return parse!(int)(s);
}

struct ProcessSource{
	string prog;
	string[] args;
	
	int fdInFromChild;
	
	short[] readShort(int count){
		short[] ret = new short[count];
		foreach(ref e; ret){
			e = readShort();
		}
		return ret;
	}
	
	short readShort(){
		if( ! fdInFromChild ){
			startProc();
		}
		//writeln(`fdInFromChild `,fdInFromChild);
		short ret;
		long readLen = core.sys.posix.unistd.read(fdInFromChild,&ret,2);
		if( readLen!=2 ){
		  writeln(`readLen `,readLen);
		}
		return ret;
	}
	
	void startProc(){
      int in_fd[2];
      pipe(in_fd);  // For child's stdout

      int pid = fork();
      if( pid==0 ){

        close(in_fd[0]);
        dup2(in_fd[1], STDOUT_FILENO);
        close(in_fd[1]);

        execvp(prog,args);
      }else{
      	fdInFromChild = in_fd[0];
      }
	}
}

void execvp_with_input(string prog, string[] args,byte[] inputData){
	execvp_with_input(prog, args, inputData, true);
}

void execvp_with_input(string prog, string[] args,byte[] inputData, bool waitForIt){
      int in_fd[2];
      pipe(in_fd);  // For child's stdin

      int pid = fork();
      if( pid==0 ){

        close(in_fd[1]);
        dup2(in_fd[0], STDIN_FILENO);
        close(in_fd[0]);

        execvp(prog,args);
      }else{
          core.sys.posix.unistd.write(in_fd[1],inputData.ptr,inputData.length);
          close(in_fd[1]);
          if( waitForIt ){
	        int status;
	        waitpid(pid, &status, 0);
	      }
      }
}

void mkGrayImage(string outFile,int w,int h, ubyte delegate(int x, int y) grayVals ){
  byte[] data = new byte[w*h];
  foreach(x; 0..w ) foreach(y; 0..h){
    data[y*w+x] = grayVals(x,y);
  }
  execvp_with_input("convert",["convert","-size",format("%sx%s",w,h),"-depth","8","gray:-",outFile],data);
}

void mkRGBImage(string outFile,int w,int h, int delegate(int x, int y) rgbVals ){
  writeln(`saving rgb `, outFile);
  byte[] data = new byte[w*h*3];
  foreach(x; 0..w ) foreach(y; 0..h){
    data[3*(y*w+x)+0] = (rgbVals(x,y) >> 16) & 0xff;
    data[3*(y*w+x)+1] = (rgbVals(x,y) >> 8) & 0xff;
    data[3*(y*w+x)+2] = rgbVals(x,y) & 0xff;
  }
  execvp_with_input("convert",["convert","-size",format("%sx%s",w,h),"-depth","8","rgb:-",outFile],data);
}

//static if (""==shell("touch 123.123")){
//int a;
//}

ImgData!ubyte readImg(string srcFile){
	string imgIdent =  shell("identify  " ~ srcFile);
	write(imgIdent);
	auto m = std.regexp.search(imgIdent, r" (\d+)x(\d+) ");

	auto img = ImgData!ubyte();
//        writeln(typeof(m.match(1)));
        img.w = toInt(m.match(1));
        img.h = toInt(m.match(2));
        img.data = cast(ubyte[]) shell("convert " ~ srcFile ~ " gray:-");

	return img;
}


struct ImgData(Pixel){
  int w;
  int h;
  Pixel[] data;
  
  ImgData!Pixel deepCopy(){
  	ImgData!Pixel ret = this;
  	ret.data = data.dup;
  	return ret;
  }
  
  static if( is( Pixel == int) ) {
	  void saveScaledUp(string fname, int by){
		mkRGBImage(fname, by*w, by*h, delegate int(int x,int y){
	    	return val(x/by,y/by); 
	    });
	  }
  } 
  
  static if( is( Pixel == ubyte) ) {
	ImgData!int asRGB(){
		ImgData!int ret;
		ret.w=w;
		ret.h=h;
		ret.data = new int[data.length];
		foreach( int i, ubyte p; data ){
		  ret.data[i] = p<<16 | p<<8 | p; 			
		}
		return ret;
	}  	

	  void saveScaledUp(string fname, int by){
		mkGrayImage(fname, by*w, by*h, delegate ubyte(int x,int y){
	    	return val(x/by,y/by); 
	    });
	  }
  }
  
  Pixel val(int x, int y){
  	return data[x + y*w];
  }
  
  bool inside(int x, int y){
  	return x>=0 && y>=0 && x<w && y<h;
  }
  
  Pixel[] circleVals(int x, int y, int radius){
  	Pixel[] ret;
  	foreach(xi; x-radius..x+radius){
		foreach(yi; y-radius..y+radius){
			if( inside(xi,yi) ){
				ret ~= val(xi,yi);
			}
		}
  	}
  	return ret;
  }
  
  ImgData!Pixel scaleDown(int by){
  	int neww = w/by;
  	int newh = h/by;
  	//writeln(neww,' ',newh);
  	ImgData!Pixel ret = ImgData!Pixel(neww, newh, new Pixel[neww*newh]);
  	foreach(x; 0..neww){
  		foreach(y; 0..newh){
  			ulong sum=0;
  			foreach(dx; 0..by){
  				foreach(dy; 0..by){
  					sum += val(x*by+dx, y*by+dy);
  				}
  			}
  			//writeln(x,' ',y,' ',ret.data.length, ' ',x + y*neww, ' ',cast(ubyte)( sum/by/by ));
  			ret.data[x + y*neww] = cast(ubyte)( sum/by/by );
  		}
  	}
  	return ret;
  }
  
}
